/*
 * Copyright (c) 2011, Marc-Andre Ferland - madrang@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the The Warrent Team nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * This software is free for non-commercial use. You may not use this
 *       software, in whole or in part, in support of any commercial product
 *       without the express consent of the author.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE WARRENT TEAM BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace OsHelper.UsbNet.Descriptors
{

	public class ConfigurationDescriptor
	{
		/// <summary>
		/// Total size of this structure in bytes.
		/// </summary>
		public static int Size {
			get { return Marshal.SizeOf (typeof(ConfigurationDescriptorHeader)); }
		}

		[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 9)]
		public class ConfigurationDescriptorHeader
		{
			[FieldOffsetAttribute(0)]
			private Descriptor BaseDesc;
			
			/// <summary>
			/// Total length of data returned for this configuration.
			/// </summary>
			[FieldOffsetAttribute(2)]
			public short TotalLength;

			/// <summary>
			/// Number of interfaces supported by this configuration.
			/// </summary>
			[FieldOffsetAttribute(4)]
			public byte NumberInterfaces;

			/// <summary>
			/// Identifier value for this configuration.
			/// </summary>
			[FieldOffsetAttribute(5)]
			public byte ConfigurationValue;

			/// <summary>
			/// Index of string descriptor describing this configuration.
			/// </summary>
			[FieldOffsetAttribute(6)]
			public byte StringIndex;

			/// <summary>
			/// Configuration characteristics.
			/// </summary>
			/// <remarks>
			/// Bits [0, 4] Reserved (reset to zero)
			/// Bit 5: Remote Wakeup
			/// Bit 6: Self-powered
			/// Bit 7: Reserved (set to one)
			/// </remarks>
			[FieldOffsetAttribute(7)]
			public byte Attributes;

			/// <summary>
			/// Maximum power consumption of the USB device from this bus
			/// in this configuration when the device is in full opreation.
			/// </summary>
			[FieldOffsetAttribute(8)]
			public byte MaxPower;
			
			public byte Length {
				get { return this.BaseDesc.Length; }
				set { this.BaseDesc.Length = value; }
			}
		
			public UsbNet.Descriptors.DescriptorType Type {
				get { return this.BaseDesc.Type; }
				set { this.BaseDesc.Type = value; }
			}
		}
		
		ConfigurationDescriptorHeader ConfigDesc;

		public ConfigurationDescriptor () : base()
		{
			this.ConfigDesc = new ConfigurationDescriptor.ConfigurationDescriptorHeader();
			this.InterfaceList = new List<InterfaceDescriptor>();
			
			this.Length = (byte)ConfigurationDescriptor.Size;
			this.Type = new DescriptorType (StandardDescriptorSubType.Configuration);
		}

		public byte[] ToBytes ()
		{
			throw new NotImplementedException ();
		}

		public static ConfigurationDescriptor FromBytes (byte[] Data)
		{
			if (Data == null)
				throw new ArgumentNullException ("Data");
			
			if (Data.Length < ConfigurationDescriptor.Size)
				throw new ArgumentException (string.Format("Data.Length: {0}, ConfigurationDescriptor.Size: {1}", Data.Length, ConfigurationDescriptor.Size));
			
			byte[] ConfDescData = new byte[ConfigurationDescriptor.Size];
			Array.Copy(Data, 0, ConfDescData, 0, ConfigurationDescriptor.Size);
			
			ConfigurationDescriptor ConfDesc = new ConfigurationDescriptor();
			ConfDesc.ConfigDesc = (ConfigurationDescriptorHeader)OsHelper.Serialyser.RawDeserialize(ConfDescData, typeof(ConfigurationDescriptorHeader));
			
			int MaxLength = Math.Min(ConfDesc.ConfigDesc.TotalLength, Data.Length);
			int Pos = 0;
			int CurrentInterface = 0;
			int FoundInterface = 0;
			DescriptorType InterfaceType = new DescriptorType (StandardDescriptorSubType.Interface);
			
			while (Pos + Descriptor.Size <= MaxLength && FoundInterface < ConfDesc.ConfigDesc.NumberInterfaces) {
				byte[] DescData = new byte[Descriptor.Size];
				Array.Copy(Data, Pos, DescData, 0, Descriptor.Size);
				Descriptor Desc = Descriptor.FromBytes (DescData);
				
				if (Desc.Length < Descriptor.Size)// || Desc.Length > MaxLength - Pos)
					throw new InvalidDescriptorException ("The device has errors in its Configuration descriptor.");
				
				if (Desc.Type == InterfaceType && CurrentInterface != 0) {
					FoundInterface++;
					
					byte[] InterfaceData = new byte[Pos - CurrentInterface];
					Array.Copy(Data, CurrentInterface, InterfaceData, 0, InterfaceData.Length);
					ConfDesc.InterfaceList.Add(InterfaceDescriptor.FromBytes (InterfaceData));
				}
				
				if (Pos + Desc.Length >= MaxLength && CurrentInterface != 0) {
					FoundInterface++;
					
					byte[] InterfaceData = new byte[MaxLength - CurrentInterface];
					Array.Copy(Data, CurrentInterface, InterfaceData, 0, InterfaceData.Length);
					ConfDesc.InterfaceList.Add(InterfaceDescriptor.FromBytes (InterfaceData));
				}
				
				if (Desc.Type == InterfaceType) {
					CurrentInterface = Pos;
				}
				
				Pos += Desc.Length;
			}
			
			return ConfDesc;
		}
		
		public byte Length {
			get { return this.ConfigDesc.Length; }
			private set { this.ConfigDesc.Length = value; }
		}
		
		public UsbNet.Descriptors.DescriptorType Type {
			get { return this.ConfigDesc.Type; }
			private set { this.ConfigDesc.Type = value; }
		}
		
		/// <summary>
		/// Total length of data returned for this configuration.
		/// </summary>
		public short TotalLength {
			get { return this.ConfigDesc.TotalLength; }
			set { this.ConfigDesc.TotalLength = value; }
		}
		
		/// <summary>
		/// Number of interfaces supported by this configuration.
		/// </summary>
		public byte NumberInterfaces {
			get { return this.ConfigDesc.NumberInterfaces ; }
			set { this.ConfigDesc.NumberInterfaces = value; }
		}
		
		/// <summary>
		/// Identifier value for this configuration.
		/// </summary>
		public byte ConfigurationValue {
			get { return this.ConfigDesc.ConfigurationValue ; }
			set { this.ConfigDesc.ConfigurationValue = value; }
		}
		
		/// <summary>
		/// Index of string descriptor describing this configuration.
		/// </summary>
		public byte StringIndex {
			get { return this.ConfigDesc.StringIndex ; }
			set { this.ConfigDesc.StringIndex = value; }
		}
		
		/// <summary>
		/// Configuration characteristics.
		/// </summary>
		/// <remarks>
		/// Bits [0, 4] Reserved (reset to zero)
		/// Bit 5: Remote Wakeup
		/// Bit 6: Self-powered
		/// Bit 7: Reserved (set to one)
		/// </remarks>
		private byte Attributes {
			get { return this.ConfigDesc.Attributes ; }
			set { this.ConfigDesc.Attributes = value; }
		}
		
		/// <summary>
		/// Maximum power consumption of the USB device from this bus
		/// in this configuration when the device is in full opreation.
		/// </summary>
		public double MaxPower {
			get { return this.ConfigDesc.MaxPower * 0.002d; }
			set { this.ConfigDesc.MaxPower = (byte)(value / 0.002d); }
		}

		public bool SelfPowered {
			get { return (this.Attributes & 64) != 0; }
			set {
				if(this.SelfPowered != value)
					this.Attributes ^= (1 << 6);
			}
		}

		public bool RemoteWakeup {
			get { return (this.Attributes & 32) != 0; }
			set {
				if(this.RemoteWakeup != value)
					this.Attributes ^= (1 << 5);
			}
		}

		/// <summary>
		/// Array of interfaces supported by this configuration.
		/// The length of this array is determined by the NumberInterfaces field.
		/// </summary>
		public List<InterfaceDescriptor> InterfaceList { get; set; }
		
	}
}
